#include <fstream>
#include <vector>
#include <bitset>
#include <algorithm>

template <typename WordType>
class LFSREngine
{
	public:
		static constexpr size_t word_size = sizeof(WordType);
		static constexpr size_t word_size_bits = word_size * 8;
		using WordBitSet = std::bitset<word_size_bits>;

	private:
		const std::vector<uint32_t> taps;
		std::vector<WordBitSet> register_;

		//Cached variables (for performance)
		const size_t register_size = register_.size();
		const size_t register_size_bits = register_size * word_size_bits;


	public:
		LFSREngine<WordType>(std::vector<uint32_t> const& taps, std::vector<WordBitSet>&& seed)
			: register_(std::move(seed))
		{}

		LFSREngine<WordType>(std::vector<uint32_t>&& taps, std::vector<WordBitSet>&& seed)
			: taps(std::move(taps)), register_(std::move(seed))
		{}

		LFSREngine<WordType>(std::vector<uint32_t>&& taps, std::vector<WordBitSet> const& seed)
			: taps(std::move(taps))
		{}

		LFSREngine<WordType>(std::vector<uint32_t> const& taps, std::vector<WordBitSet> const& seed)
		{
			register_ = seed;
		}

		WordType operator()()
		{
			WordBitSet word;

			for(size_t bit = 0; bit < word_size_bits; bit++) {
				bool shift_bit = 0;
				for (const auto &tap : taps) {
					const auto tap_ = tap + bit;
					if (tap_ >= register_size_bits)
					{
						shift_bit ^= word[tap_ % register_size_bits];
					}
					else
					{
						size_t register_element = tap_ / word_size_bits;
						size_t register_bit = tap_ % word_size_bits;
						shift_bit ^= register_[register_size - 1 - register_element][register_bit];
					}
				}

				word[bit] = shift_bit;
			}
			register_.insert(register_.begin(), word);
			word = register_.back(); //Now word is used as tmp variable
			register_.pop_back();
			return word.to_ullong();
		}

		void discard();
};

int main()
{
	using word_t = uint8_t;

	std::vector<uint32_t> polynomial = {1, 2, 3, 5, 6, 8, 15, 19, 28, 33, 55};
	std::vector<std::bitset<8>> reg = {0x64, 0xE9, 0x89, 0x1A, 0x5B, 0xC5, 0xDE, 0xE3};
	//std::vector<std::bitset<16>> reg = {0x64E9, 0x891A, 0x5BC5, 0xDEE3};
	//std::vector<std::bitset<32>> reg = {0x64E9891A, 0x5BC5DEE3};
	//std::vector<std::bitset<64>> reg = {0x64E9891A5BC5DEE3};

	LFSREngine engine = LFSREngine<word_t>(std::move(polynomial), std::move(reg));

	uint64_t megabyte = 1024*1024;
	uint64_t file_size = megabyte / sizeof(word_t); //in words

	std::ofstream file("output.dat");

	for (size_t i = 0; i < file_size; i++) {
		file << engine();
	}

	file.close();

	return 0;
}
